package com.nulldev.util.languages.csv.writer;

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;

/**
 * This is the main class for writing CSV data.
 *
 * @author Oliver Siegmar
 */
public final class CsvAppender implements Closeable, Flushable {

	private static final char LF = '\n';
	private static final char CR = '\r';

	private final Writer writer;
	private final char fieldSeparator;
	private final char textDelimiter;
	private final boolean alwaysDelimitText;
	private final char[] lineDelimiter;

	private boolean newline = true;

	CsvAppender(final Writer writer, final char fieldSeparator, final char textDelimiter, final boolean alwaysDelimitText, final char[] lineDelimiter) {
		this.writer = new FastBufferedWriter(writer);
		this.fieldSeparator = fieldSeparator;
		this.textDelimiter = textDelimiter;
		this.alwaysDelimitText = alwaysDelimitText;
		this.lineDelimiter = lineDelimiter;
	}

	/**
	 * Appends a field to the current row. Automatically adds field separator and
	 * text delimiters as required.
	 *
	 * @param value the field to append ({@code null} is handled as empty string)
	 * @throws IOException if a write error occurs
	 */
	public void appendField(final String value) throws IOException {
		if (!newline) {
			writer.write(fieldSeparator);
		} else {
			newline = false;
		}

		if (value == null) {
			if (alwaysDelimitText) {
				writer.write(textDelimiter);
				writer.write(textDelimiter);
			}
			return;
		}

		boolean needsTextDelimiter = alwaysDelimitText;
		boolean containsTextDelimiter = false;

		for (int i = 0; i < value.length(); i++) {
			final char c = value.charAt(i);
			if (c == textDelimiter) {
				containsTextDelimiter = needsTextDelimiter = true;
				break;
			} else if (c == fieldSeparator || c == LF || c == CR) {
				needsTextDelimiter = true;
			}
		}

		if (needsTextDelimiter) {
			writer.write(textDelimiter);
		}

		if (containsTextDelimiter) {
			for (int i = 0; i < value.length(); i++) {
				final char c = value.charAt(i);
				if (c == textDelimiter) {
					writer.write(textDelimiter);
				}
				writer.write(c);
			}
		} else {
			writer.write(value);
		}

		if (needsTextDelimiter) {
			writer.write(textDelimiter);
		}
	}

	/**
	 * Appends a complete line - one or more fields and new line character(s) at the
	 * end.
	 *
	 * @param values the fields to append ({@code null} values are handled as empty
	 *               strings)
	 * @throws IOException if a write error occurs
	 */
	public void appendLine(final String... values) throws IOException {
		for (final String value : values) {
			appendField(value);
		}
		endLine();
	}

	/**
	 * Appends new line character(s) to the current line.
	 *
	 * @throws IOException if a write error occurs
	 */
	public void endLine() throws IOException {
		writer.write(lineDelimiter);
		newline = true;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void close() throws IOException {
		writer.close();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void flush() throws IOException {
		writer.flush();
	}

}